# coding=utf-8
'''
董新强
日志记录
'''
import asyncio
import functools
import logging
from datetime import datetime

from enums.enum_error_type import EnumErrorType
from framework.block_redis_list_helper import BlockRedisListHelper
from framework.msg import failure, ok
from framework.redis_helper import ReidsHelper
from framework.utilities import to_int, to_string, to_datetime, DATETIME_PATTERN, echo, to_list, time_str
from setting import is_debug


def trycatch(fail=failure(msg='操作失败'), roles=None, model=None, unulls: tuple = None):
    """
    异常捕获
    :param model: 用来校验参数的模型
    :param unulls: 如果函数的一个个参数是字典,并且指定要检测的字段,则检查字段
    :param fail: 失败时候返回值
    :param roles:角色列表/元组
    """

    def inner_permission(function):
        @functools.wraps(function)
        def warp(*args, **kwargs):
            try:
                if asyncio.iscoroutinefunction(function):
                    logging.error('错误=>{}是协程函数'.format(function))
                    return failure(msg='服务器内部错误')

                if args and len(args) > 1:
                    precheck_result = precheck_params(model, unulls, args[1])
                    if not precheck_result.is_ok(): return precheck_result

                # -------角色权限验证-----------------------------------------------------
                if roles and isinstance(roles, (tuple, list)):
                    usr_role = args[0].usr['role']
                    has_permission = False
                    for role in roles:
                        if role == usr_role:
                            has_permission = True
                            break
                    if not has_permission:
                        return failure(msg='没有权限', error_type=EnumErrorType.access_denied)

                return function(*args, **kwargs)
            except Exception as e:
                log_block(e, '{}; 传入参数: arg=>{},kwargs=>{}'.format(function, args, kwargs))
            return fail

        return warp

    return inner_permission


def log_block(e, except_position='代码中未指定', params=None):
    if not e:
        return

    try:
        body = _format_expception(e, except_position, params)
        if is_debug:
            print(body)
        else:
            BlockRedisListHelper.append('err-list', body)
    except Exception as e:
        logging.error('log_controller.err=>' + str(e))


async def log_async(e, except_position='代码中未指定', params=None):
    if not e: return

    try:
        body = _format_expception(e, except_position, params)
        if is_debug:
            print(body)
        else:
            await ReidsHelper().append('err-list', body)
    except Exception as e:
        logging.error('log_controller.err=>' + str(e))


def precheck_params(model, unulls, dic):
    '''
    参数预先检测,优先级如下:
        model不为空,根据model来检测
        model为空,根据unulls元素类型检测.
    :param model:
    :param unulls:
    :param dic:
    :return:
    '''
    if not unulls or not isinstance(dic, dict): return ok()

    try:
        for f in unulls:
            if model:  # 模型不为空,按模型啦检测
                if f not in dic:
                    echo('precheck_params=>%s为空' % (f,))
                    return failure(error_type=EnumErrorType.param_error)
                tp, value = getattr(model, f).expression.type.python_type, dic.get(f)
                result = format_field_type(tp, f, dic)
                if not result.is_ok(): return result
            else:
                # 只需要检测是否有指定的字段
                if not isinstance(f, tuple):
                    if f not in dic:
                        echo('precheck_params=>%s为空' % (f,))
                        return failure(error_type=EnumErrorType.param_error)
                    continue

                # 不仅需要检测字段,还需要检测值
                field, field_type = f
                if field not in dic:
                    echo('precheck_params=>%s为空' % (f,))
                    return failure(error_type=EnumErrorType.param_error)
                result = format_field_type(field_type, field, dic)
                if not result.is_ok(): return result

    except Exception as e:
        echo('precheck_params=>', e)
        return failure(error_type=EnumErrorType.param_error)
    return ok()


def format_field_type(field_type, field, dic):
    # int类型
    if field_type is int:
        value = to_int(dic[field])
        if value is None:
            return failure(msg='数字格式错误')
        else:
            dic[field] = value
        return ok()

    # 如果是时间类型
    if field_type is datetime:
        value = DATETIME_PATTERN.match(to_string(dic[field]))
        if not value: return failure(msg='日期格式错误', error_type=EnumErrorType.param_error)
        value = to_datetime(value.group())
        if not value:
            return failure(msg='请检查必填字段', error_type=EnumErrorType.param_error)
        else:
            dic[field] = value
        return ok()

    # 如果是列表
    if field_type is list:
        ls = to_list(dic[field])
        if not ls:
            return failure(msg='{}不能为空'.format(field))
        else:
            dic[field] = ls
        return ok()

    return ok()


def _format_expception(e, except_position, params=None):
    dic = {
        'TYPE': 1 if is_debug else 0,
        'ID': 'CAT',
        '异常': to_string(e, '未获到异常结果'),
        '位置': except_position.replace('<', '').replace('>', '') if except_position else '未捕捉到位置',
        '时间': time_str(datetime.now()),
        '参数': to_string(params, '无')
    }
    return dic


if __name__ == '__main__':
    async def div(x, y):
        await asyncio.sleep(5)
        return x / y


    class ErrorTest:
        @trycatch(failure(msg='失败'))
        def divb(self, x, y):
            return x / y


    print(ErrorTest().divb(1, 0))
